﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using UniLua;

namespace TCG
{
    /// <summary>
    /// UniLua相关的操作.
    /// </summary>
    public static class LuaTools
    {
		/// <summary>
        /// 返回和<paramref name="luaState"/>关联的当前栈的内容，如果是在被lua调用的C#函数内部，则
        /// 返回局部栈的内部，否则返回全局栈的内容
        /// </summary>
        public static string LuaDumpStack (ILuaState aluaState)
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder ();
            sb.AppendLine ("当前栈快照 ----------------------------------------");
            int top = aluaState.GetTop ();
            sb.AppendFormat ("栈大小{0}{1}", top, System.Environment.NewLine);

            for (int i = top; i >= 1; -- i)
            {
                LuaType luaType = aluaState.Type (i);
                sb.Append (string.Format ("元素: {0}/{1}/{2} ", -(top + 1 - i), i, luaType));
                switch (luaType)
                {
                    case LuaType.LUA_TBOOLEAN:
                        sb.Append (aluaState.ToBoolean (i));
                        break;

                    case LuaType.LUA_TDEADKEY:
                        sb.Append ("Deadkey");
                        break;

                    case LuaType.LUA_TFUNCTION:
                        sb.Append (aluaState.ToString (i));
                        break;

                    case LuaType.LUA_TLIGHTUSERDATA:
                        sb.Append (aluaState.ToString (i));
                        break;

                    case LuaType.LUA_TNIL:
                        sb.Append ("Nil");
                        break;

                    case LuaType.LUA_TNONE:
                        sb.Append ("None");
                        break;

                    case LuaType.LUA_TNUMBER:
                        sb.Append (aluaState.ToNumber (i));
                        break;

                    case LuaType.LUA_TPROTO:
                        sb.Append ("Proto");
                        break;

                    case LuaType.LUA_TSTRING:
                        sb.Append (aluaState.ToString (i));
                        break;

                    case LuaType.LUA_TTABLE:
                        sb.Append (aluaState.ToString (i));
                        break;

                    case LuaType.LUA_TTHREAD:
                        sb.Append (aluaState.ToString (i));
                        break;

                    case LuaType.LUA_TUINT64:
                        sb.Append (aluaState.ToUInt64 (i));
                        break;

                    case LuaType.LUA_TUPVAL:
                        sb.Append (aluaState.ToString (i));
                        break;

                    case LuaType.LUA_TUSERDATA:
                        sb.Append (aluaState.ToString (i));
                        break;
                }
                sb.Append (System.Environment.NewLine);
            }
            sb.AppendLine ("栈快照结束 ------------------------------------");

            return sb.ToString ();
        }

		/// <summary>
		/// 导出ffi接口供lua调用。被导出的函数分为两部分，libFuncs中保存接口的静态方法，memberFuncs中保存接口的成员方法。
		/// memberFuncs被注册在LUA_REGISTRYINDEX表中名为libName的表上，该表作为ffi接口对象的metatable，为ffi接口创建的
		/// userdata提供面向对象的成员访问方式。
		/// </summary>
		public static void ExportLib (ILuaState luaState, string libName, IList<NameFuncPair> libFuncs, IList<NameFuncPair> memberFuncs)
		{
			// 成员函数位于metatable中，这样调用userdata.operation(...)时，就会在该metatable.__index中查找
			LuaTools.L_NewMetaTable (luaState, libName);
			luaState.PushValue (-1);
			luaState.SetField (-2, "__index");
			luaState.L_SetFuncs (memberFuncs, 0);
			
			// 静态函数和构造函数被导出
			luaState.L_NewLib (libFuncs);
		}

		/// <summary>
		/// 导出ffi接口供lua调用。和ExportLib不同的是，该函数会将被导出类的metatable.__index.__index指向<paramref name="parentLibName"/>
		/// 所属库的metatable.__index表，以在lua端支持被导出userdata的继承关系。
		/// </summary>
		public static void ExportLibWithInheritance (ILuaState luaState, string libName, IList<NameFuncPair> libFuncs, IList<NameFuncPair> memberFuncs,
		                                 string parentLibName)
		{
			// metable for userdata
			LuaTools.L_NewMetaTable (luaState, libName);

			// __index table of metatable
			luaState.NewTable ();
			luaState.PushValue (-1);
			luaState.SetField (-3, "__index");

			// now -1 is __index table, -2 is metatable, set member functions into __index table
			luaState.L_SetFuncs (memberFuncs, 0);

			// get metatable of parent's lib
			L_GetMetaTable (luaState, parentLibName);
			if (luaState.IsNil (-1))
			{
				// no metatable found for parentlib
				throw new Exception (string.Format ("ExportLibWithInheritance {0}: no metatable table found of parent lib {1}", libName, parentLibName));
			}
			if (!luaState.IsTable (-1))
			{
				throw new Exception (string.Format ("ExportLibWithInheritance {0}: metatable is not a table of parent lib {1}", libName, parentLibName));
			}

			// get __index table of metatable of parent's lib
			luaState.PushString ("__index");
			luaState.RawGet (-2);
			if (luaState.IsNil (-1))
			{
				// no __index table found for parent metatable
				throw new Exception (string.Format ("ExportLibWithInheritance {0}: no __index table found for metatable of parent lib: {0}", libName, parentLibName));
			}
			if (!luaState.IsTable (-1))
			{
				throw new Exception (string.Format ("ExportLibWithInheritance {0}: __index is not a table for metatable of parent lib: {1}", libName,  parentLibName));
			}

			// set __index table of userdata's metatable to __index table of metatable of parent's lib
			luaState.PushString ("__index");
			luaState.PushValue (-2);
			luaState.RawSet (-5);

			// export static members
			luaState.L_NewLib (libFuncs);

		}

		/// <summary>
		/// 向lua压入一个userdata，并且将其和名为metaTableName的元表相关联。
		/// </summary>
		public static void L_PushUserData (ILuaState luaState, object vo, string metaTableName)
		{
			luaState.PushUserData (vo);
			L_SetMetaTable (luaState, -1, metaTableName);
		}

		/// <summary>
		/// <para>UniLua没有提供luaL_newmetatable的实现。按照lua5.2文档，其行为如下：</para>
		/// <para>If the registry already has the key tname, returns 0. Otherwise, creates a new table to be used as a metatable for userdata, 
		/// adds it to the registry with key tname, and returns 1.</para>
		/// <para>In both cases pushes onto the stack the final value associated with tname in the registry.</para>
		/// </summary>
		public static int L_NewMetaTable (ILuaState luaState, string tname)
		{
			luaState.PushString (tname);
			luaState.RawGet (LuaDef.LUA_REGISTRYINDEX);
			if (luaState.IsNil (-1))
			{
				// pop nil 
				luaState.Pop (1);

				// create new and pushs on the stack
				luaState.NewTable ();
				luaState.PushString (tname);
				luaState.PushValue (-2);
				luaState.RawSet (LuaDef.LUA_REGISTRYINDEX);
				return 1;
			}
			else 
			{
				if (luaState.IsTable (-1))
				{
					return 0;
				}
				else
				{
					throw new System.Exception (string.Format ("NewMetaTable: lua registry 中键值{0}关联的并非table类型", tname));
				}

			}
		}

		/// <summary>
		/// Set metatable of element at stackPos in stack to the one associated with name tname in the registry.
		/// </summary>
		public static void L_SetMetaTable (ILuaState luaState, int stackPos, string tname )
		{
			L_GetMetaTable (luaState, tname);
			if (stackPos >= 0)
			{
				luaState.SetMetaTable (stackPos);
			}
			else
			{
				luaState.SetMetaTable (stackPos - 1);
			}
		}

		/// <summary>
		/// <para>UniLua没有提供luaL_getmetatable的实现，按照lua.5.2文档，其行为如下：</para>
		/// <para>Pushes onto the stack the metatable associated with name tname in the registry</para>
		/// </summary>
		public static void L_GetMetaTable (ILuaState luaState, string tname)
		{
			luaState.PushString (tname);
			luaState.RawGet (LuaDef.LUA_REGISTRYINDEX);
		}

		/// <summary>
		/// <para>lua的C-API提供luaL_checkudata，是因为c向lua提供的userdata是纯内存块，为了防止不同类型的数据被误操作产生内存错误，
		/// 才需要检测和其相关联的metatable，在c#中，如果是不同类型的对象，且不在同一继承链中，外部as会失败，因此不需要检查。</para>
		/// <para>UniLua没有提供luaL_checkudata的实现，按照lua.5.2文档，其行为如下：</para>
		/// <para>luaL_checkudata checks whether the object at the given stack position is userdata with a metatable that matches 
		/// the given name. It raises an error if the object does not have the correct metatable or if it is not userdata, 
		/// otherwise, it returns the userdata address.</para>
		/// </summary>
		public static object L_CheckUData (ILuaState luaState, int stackPos, string metaTableName)
		{
			object userdata = luaState.ToUserData (stackPos);
			if (userdata == null) throw new System.Exception (string.Format ("CheckUData: 索引{0}处的元素不是userdata", stackPos));

			/*
			if (!luaState.GetMetaTable (stackPos)) throw new System.Exception (string.Format ("CheckUData: 索引{0}处的元素没有绑定metatable", stackPos));

			// 栈顶是stackpos处的元素帮定的metatable

			L_GetMetaTable (luaState, metaTableName);

			// 现在栈顶是metaTableName对应的metatable，或者是nil
			if (luaState.Compare (-1, -2, LuaEq.LUA_OPEQ))
			{
				luaState.Pop (2);
				return userdata;
			}
			else
			{
				throw new System.Exception (string.Format ("CheckUData: 索引{0}处的元素绑定metatable与名为{1}的metatable不相同", stackPos, metaTableName));
			}
			*/
			return userdata;
		}

		/// <summary>
		/// 从索引<paramref name="tableIndex"/>处获取table，并将其<paramref name="functionName"/>
		/// 方法注册到<code>LUA_REGISTRYINDEX</code>中，返回注册后的引用
		/// </summary>

		public static int RefLuaFunction (ILuaState luaState, int tableIndex, string functionName)
		{
			luaState.GetField (tableIndex, functionName);
			if (!luaState.IsFunction (-1))
			{
				throw new Exception (string.Format ("method {0} not found!", functionName));
			}
			return luaState.L_Ref (LuaDef.LUA_REGISTRYINDEX);
		}

		/// <summary>
		/// 调用注册在<code>LUA_REGISTRYINDEX</code>中引用值为<paramref name="functionRef"/>的lua函数。
		/// </summary>

		public static void CallLuaRefFunction (ILuaState luaState, int functionRef, int numArgs, int numResults, int errorFunc)
		{
			luaState.RawGetI (LuaDef.LUA_REGISTRYINDEX, functionRef);
			if (numArgs > 0)
			{
				luaState.Insert (-1 - numArgs);
			}

			// push error function
			luaState.PushCSharpFunction (TCG.FFI.FFIToolsLib.lf_LuaLStackTrace);
			luaState.Insert (-1 - numArgs - 1);

			ThreadStatus ret = luaState.PCall (numArgs, numResults, -1 - numArgs - 1);
			if (ret != ThreadStatus.LUA_OK)
			{
				Debug.LogError (luaState.ToString (-1));

				// remove error function
				luaState.Remove (-2);
			}
			else
			{
				// remove error function
				luaState.Remove (-numResults - 1);
			}
		}

    }
}